﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using NumericalData;
using NumericalData.Systems;
using NumericalData.SolvationMethods;
using NumericalData.SolvationMethods.Async;
using WPFSolution.CommonOperations;
using Vector = NumericalData.Vector;
using System.Windows.Forms;
using MessageBox = System.Windows.MessageBox;
using Orientation = System.Windows.Controls.Orientation;
using TextBox = System.Windows.Controls.TextBox;
using ToolTip = System.Windows.Controls.ToolTip;
using UserControl = System.Windows.Controls.UserControl;

namespace WPFSolution.UserControls
{
    /// <summary>
    /// User Control
    /// Allow to operate with systems
    /// </summary>
    public partial class MatrixCalculator : UserControl
    {
        #region Dependency Properties

        #region StatusString

        public static readonly DependencyProperty StatusStringProperty =
            DependencyProperty.Register("StatusString", typeof(string), typeof(MatrixCalculator), new PropertyMetadata(default(string)));
        /// <summary>
        /// Current Control Status
        /// </summary>
        public string StatusString
        {
            get { return (string)GetValue(StatusStringProperty); }
            set { SetValue(StatusStringProperty, value); }
        }
        #endregion
        #region IsBusy

        public static readonly DependencyProperty IsBusyProperty =
            DependencyProperty.Register("IsBusy", typeof(bool), typeof(MatrixCalculator), new PropertyMetadata(default(bool)));
        /// <summary>
        /// Calculation Status
        /// </summary>
        public bool IsBusy
        {
            get { return (bool)GetValue(IsBusyProperty); }
            set { SetValue(IsBusyProperty, value); }
        }
        #endregion

        #endregion

        #region Constructors

        public MatrixCalculator()
        {
            InitializeComponent();
            CreateEmptyCells(GetCurrentMatrixDimention());
            ChangeStatus("");
        }
        #endregion

        private void ChangeStatus(string newStatus)
        {
            if (newStatus == "")
            {
                StatusString = "Matrix Calculator Is Ready";
                IsBusy = false;
            }
            else
            {
                StatusString = newStatus;
                IsBusy = true;
            }
        }


        #region System Cells Operations

        private TextBox CreateCell(string text, string tag)
        {
            return new TextBox
            {
                BorderThickness = new Thickness(2, 2, 2, 5),
                Padding = new Thickness(5, 5, 5, 5),
                Margin = new Thickness(10, 10, 10, 10),
                Width = 60,
                Tag = tag,
                Text = text,
                ToolTip = new ToolTip() { Content = tag }
            };
        }

        void FillSystem(SquareMatrix matr, Vector vec, int dimention, bool empty)
        {
            if (DimentionsNumberTextBox != null && MatrixTemplatePanel != null && VectorTemplatePanel != null)
            {
                MatrixTemplatePanel.Children.Clear();
                VectorTemplatePanel.Children.Clear();
                GC.Collect();
                for (var i = 0; i < dimention; ++i)
                {
                    var rowPanel = new StackPanel { Orientation = Orientation.Horizontal };
                    for (var j = 0; j < dimention; ++j)
                    {
                        rowPanel.Children.Add(
                            CreateCell(empty ? "" : matr[i, j].ToString(), i + "," + j));
                    }
                    MatrixTemplatePanel.Children.Add(rowPanel);
                    VectorTemplatePanel.Children.Add(CreateCell(empty ? "" : vec[i].ToString(), i.ToString()));
                }
            }
        }

        void CreateEmptyCells(int matrixDimention)
        {
            FillSystem(new SquareMatrix(), new Vector(), matrixDimention, true);
        }

        int GetSystemDimention()
        {
            if (MatrixTemplatePanel != null)
            {
                return MatrixTemplatePanel.Children.Count;
            }
            return 0;
        }

        #region Resize System
        void ResizeSystem(int newDimention)
        {
            if (MatrixTemplatePanel != null && VectorTemplatePanel != null)
            {
                int currentDim = GetSystemDimention();
                if (currentDim < newDimention)
                    AddNewCells(currentDim, newDimention);
                else
                    RemoveOldCells(currentDim, newDimention);
            }
            GC.Collect();
        }

        private void RemoveOldCells(int currentDim, int newDimention)
        {
            //Remove Rows
            MatrixTemplatePanel.Children.RemoveRange(newDimention, currentDim - newDimention);
            VectorTemplatePanel.Children.RemoveRange(newDimention, currentDim - newDimention);
            //RemoveColumns
            for (int i = 0; i < newDimention; ++i)
            {
                var row = (StackPanel)MatrixTemplatePanel.Children[i];
                row.Children.RemoveRange(newDimention, currentDim - newDimention);
            }
        }

        void AddNewCells(int currentDim, int newDimention)
        {
            // Add new rows
            for (int i = 0; i < (newDimention - currentDim); ++i)
            {
                var row = new StackPanel { Orientation = Orientation.Horizontal };
                for (int j = 0; j < newDimention; ++j)
                {
                    row.Children.Add(CreateCell("", (i + currentDim) + "," + j));
                }
                MatrixTemplatePanel.Children.Add(row);
                VectorTemplatePanel.Children.Add(CreateCell("", (i + currentDim).ToString()));
            }
            // Add cells to columns
            for (int i = 0; i < currentDim; ++i)
            {
                var row = (StackPanel)MatrixTemplatePanel.Children[i];
                for (int j = currentDim; j < newDimention; ++j)
                {
                    row.Children.Add(CreateCell("", i + "," + j));
                }
            }
        }
        #endregion
        #endregion

        #region Get Vector & Matrix

        double GetCellValue(TextBox cell)
        {
            if (cell != null)
            {
                double var = 0.0;
                Double.TryParse(cell.Text, out var);
                return var;
            }
            return 0.0;
        }

        private Vector GetVectorFromCells()
        {
            List<double> elements = new List<double>();
            foreach (TextBox textBox in VectorTemplatePanel.Children)
            {
                elements.Add(GetCellValue(textBox));
            }
            return new Vector(elements.ToArray());
        }

        private SquareMatrix GetMatrixFromCells()
        {
            List<double[]> rows = new List<double[]>();
            foreach (StackPanel row in MatrixTemplatePanel.Children)
            {
                List<double> elements = new List<double>();
                foreach (TextBox textBox in row.Children)
                {
                    elements.Add(GetCellValue(textBox));
                }
                rows.Add(elements.ToArray());
            }

            return new SquareMatrix(rows.ToArray());
        }

        private bool GetSystemFromFile(ref SquareMatrix matr, ref Vector vec, ref int dimention)
        {
            using (var fileDialog = new OpenFileDialog())
            {
                fileDialog.AutoUpgradeEnabled = true;
                fileDialog.CheckFileExists = true;
                fileDialog.CheckPathExists = true;
                fileDialog.Title = "Load File With System";
                fileDialog.DefaultExt = ".txt, .csv";
                fileDialog.Filter = "Text files|*.txt";// "|(*.csv)";
                DialogResult result = fileDialog.ShowDialog();
                if (result == DialogResult.OK)
                {
                    try
                    {
                        FileOperations.LoadMatrixAndVectorFromFile(fileDialog.FileName, ref matr, ref vec);
                        dimention = matr.Dimention;
                        DimsAmountSlider.Value = matr.Dimention;
                        return true;
                    }
                    catch (Exception exc)
                    {
                        MessageBox.Show("Operation failed:\n" + exc.Message, "An error occurred!",
                            MessageBoxButton.OK, MessageBoxImage.Error);
                        return false;
                    }
                }
                return false;
            }
        }

        #endregion

        #region TextBoxes values
        double GetAverageMatrixElementValue()
        {
            return GUIManipulator.GetDoubleValue(AverageMatrixElementValueTextBox, 10.0, 10000.0, 0.1);
        }

        double GetAccuracy()
        {
            return GUIManipulator.GetDoubleValue(AccuracyTextBox, 0.001, 0.01, 0.000001);
        }

        int GetMaximumIterations()
        {
            return GUIManipulator.GetIntValue(MaximumIterationsTextBox, 10000, 100000, 1000);
        }


        #endregion


        int GetCurrentMatrixDimention()
        {
            if (DimsAmountSlider != null)
                return (int)DimsAmountSlider.Value;
            return 3;
        }

        #region Controls Events

        private void DimsAmountSlider_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (DimsAmountSlider != null)
            {
                if (!SaveElementsCheckBox.IsChecked.Value)
                    CreateEmptyCells(GetCurrentMatrixDimention());
                else
                    ResizeSystem(GetCurrentMatrixDimention());
            }
        }

        private void AverageMatrixElementValueTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (AverageMatrixElementValueTextBox != null)
            {
                AverageMatrixElementValueTextBox.Text = GetAverageMatrixElementValue().ToString();
            }
        }

        private void AccuracyTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (AccuracyTextBox != null)
            {
                AccuracyTextBox.Text = GetAccuracy().ToString();
            }
        }

        private void MaximumIterationsTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (MaximumIterationsTextBox != null)
            {
                MaximumIterationsTextBox.Text = GetMaximumIterations().ToString();
            }
        }

        private void MatrixFillButton_Click(object sender, RoutedEventArgs e)
        {
            if (MatrixFillButton != null && FillMethodComboBox != null)
            {
                ChangeStatus("Filling Matrix");
                var choosen = (ListBoxItem)FillMethodComboBox.SelectedItem;
                int dim = GetCurrentMatrixDimention();
                double avg = GetAverageMatrixElementValue();
                var vec = Vector.Generate(dim, avg);
                SquareMatrix matr = null;
                switch (choosen.Name)
                {
                    case ("DiagonalDominanceFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateMatrixWithDiagonalDominance(dim, avg);
                        break;
                    case ("IdentityFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.IdentityMatrix(dim);
                        break;
                    case ("SimpleFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateSimpleMatrix(dim, avg);
                        break;
                    case ("ZeroEnterFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateZeroEnterMatrix(dim, avg);
                        break;
                    case ("DiagonalZeroEnterFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateZeroEnterOnDiagonalMatrix(dim, avg);
                        break;
                    case ("DiagonalFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateDiagonalMatrix(dim, avg);
                        break;
                    case ("LinearFreeFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateLinearFreeMatrix(dim, avg);
                        break;
                    case ("PositiveDefinedFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GeneratePositiveDefinedMatrix(dim, avg);
                        break;
                    case ("DiagonalDominateFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateDiagonalDominateMatrix(dim, avg);
                        break;
                    case ("PositiveDiagonalFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GeneratePositiveDiagonalMatrix(dim, avg);
                        break;
                    case ("ImplicitDiagonalDominanceFiller"):
                        matr =
                            MatrixGenerators<SquareMatrix>.GenerateMatrixWithImplicitDiagonalDominance(dim, avg);
                        break;
                    case ("FileContentFiller"):
                        if (!GetSystemFromFile(ref matr, ref vec, ref dim))
                        {
                            ChangeStatus("");
                            return;
                        }
                        break;
                }
                FillSystem(matr, vec, dim, false);
                ChangeStatus("");
            }
        }

        #endregion

        #region Solvation
        private void SolveButton_Click(object sender, RoutedEventArgs e)
        {
            var matr = new SquareMatrix(GetMatrixFromCells());
            var vec = new Vector(GetVectorFromCells());
            var sys = new SLAE(matr, vec);
            var maxIterations = GetMaximumIterations();
            var startVecIsZero = StartVectorIsZeroCheckBox.IsChecked.GetValueOrDefault(false);
            var obtainedAnswerAction = new Action<DiagnosticsAnswer>(AnswerObtained);
            var exceptionCatchedAction = new Action<Exception>(AnswerObtainingException);
            var calculationCanceledAction = new Action<OperationCanceledException>(CalculationCanceled);
            var solvationMethod = MethodsComboBox.SelectedMethod;
            if (solvationMethod != null)
                solvationMethod.AsyncSolve(sys, GetAccuracy(), startVecIsZero, maxIterations, obtainedAnswerAction,
                                           exceptionCatchedAction, calculationCanceledAction);
            ChangeStatus("Calculating Result");
        }

        void AnswerObtained(DiagnosticsAnswer ans)
        {
            ChangeStatus("");
            AnswerGroupBox.SetAnswer(ans,true);
            AnswerGroupBox.Visibility = Visibility.Visible;
        }

        void AnswerObtainingException(Exception exc)
        {
            ChangeStatus("");
            AnswerGroupBox.Visibility = Visibility.Collapsed;
            MessageBox.Show("Requested Matrix Can't Be Solved Using Selected Method!\n" +
                "Try Another Fill Method", "An error occurred!",
                            MessageBoxButton.OK, MessageBoxImage.Error);
        }

        void CalculationCanceled(OperationCanceledException exc)
        {
            return;
        }
        #endregion

    }

}
